package cz.possoft.explorer.meta;

import java.util.Arrays;
import java.util.List;

import cz.possoft.explorer.meta.builder.DomainMetadataException;
import cz.possoft.explorer.resolver.ArrayResolverImpl;
import cz.possoft.explorer.resolver.FieldResolverImpl;
import cz.possoft.explorer.resolver.Resolver;
import cz.possoft.explorer.resolver.method.MethodKey;
import cz.possoft.explorer.resolver.method.MethodResolver;
import cz.possoft.explorer.resolver.method.MethodResolverImpl;

/**
 * LazyQueryDomainMetadata can be used to initialize ClassMetadata of requested classes with default values 
 * even if these classes are not configured in domain.xml.
 * 
 * @author <a href="mailto:mposolda@gmail.com">Marek Posolda</a>
 * @version $Revision$
 */
public class LazyQueryDomainMetadata extends DomainMetadata
{
   // These classes should be ignored and it should always return null metadata for these classes
   private static final List<Class<?>> IGNORED_CLASSES = Arrays.asList(new Class<?>[] { String.class, int.class, Integer.class, 
         long.class, Long.class, boolean.class, Boolean.class, char.class, Character.class, double.class, Double.class });   
   
   /**
    * Try to obtain classMetadata from configuration (predecessor). If our class can't be found by predecessor 
    * we need to init ClassMetadata with default values.
    */
   public <C,T> ClassMetadata<C,T> getClassMetadata(Class<C> clazz)
   {
      ClassMetadata<C,T> current = super.getClassMetadata(clazz);
      
      // return obtained value if not null
      if (current != null)
      {
         return current;
      }
      
      if ((clazz == null) || (IGNORED_CLASSES.contains(clazz)))
      {
         return null;
      }
      
      // try to obtain default values for classMetadata, Resolver and MethodResolver according to our class.
      Class<? extends ClassMetadata<C,T>> clMetadataClass = getDefaultClassMetadataClass(clazz);
      Resolver<C,T> resolver = (Resolver<C,T>)getDefaultResolver(clazz);
      MethodResolver<C,MethodKey> methodResolver = getDefaultMethodResolver(clazz);      
      
      // add our class to DomainMetadata
      try
      {
         addClassWithAllSupertypes(clazz, resolver, clMetadataClass, methodResolver);
      }
      catch (DomainMetadataException dme)
      {
         throw new RuntimeException(dme);
      }
      
      // now our ClassMetadata are registered
      return super.getClassMetadata(clazz);
   }  
   
   /**
    * Estimation for returning most suitable ClassMetadata according to given class.
    * 
    * @param <C>
    * @param <T>
    * @param clazz
    * @return default class for ClassMetadata according to clazz parameter
    */
   protected <C,T> Class<? extends ClassMetadata<C,T>> getDefaultClassMetadataClass(Class<C> clazz)
   {
      // TODO: Investigate type safety
      
      // this means that we have array class
      if (clazz.getName().startsWith("[L"))
      {
         // TODO: workaround for type safety. We need to do it better.
         Object o = DelegateToResolverClassMetadata.class;
         return (Class<? extends ClassMetadata<C,T>>)o;
      }
      // return class ClassMetadata by default
      else
      {
         Object o = ClassMetadata.class;
         return (Class<? extends ClassMetadata<C,T>>)o;
      }
   }
   
   /**
    * Estimation for returning most suitable resolver according to given class.
    * 
    * @param <C>
    * @param clazz
    * @return default implementation of resolver according to given class
    */
   protected <C> Resolver<C,?> getDefaultResolver(Class<C> clazz)
   {            
      // this means that we have array class
      if (clazz.getName().startsWith("[L"))
      {
         // TODO: why ArrayResolverIMpl<C,?? can't be instantiated?
         return new ArrayResolverImpl();
      }

      // return class FieldResolverImpl by default
      else
      {
         return new FieldResolverImpl<C>();
      }            
   }

   /**
    * Estimation for returning most suitable method resolver according to given class.
    * 
    * @param <C>
    * @param clazz
    * @return default implementation of method resolver according to given class
    */
   protected <C> MethodResolver<C,MethodKey> getDefaultMethodResolver(Class<C> clazz)
   {
      return new MethodResolverImpl<C>();
   }
}

